ServiceWork addEventListener('fetch') 在 Chrome 与 Safari 中表现不同
•
# ServiceWork addEventListener('fetch') 在 Chrome 与 Safari 中表现不同
先决条件: Chrome 120 | Safari 17.4
最近在一个项目中发现了奇怪的问题,请求静态资源有 Cache-Control 的情况下,Chrome 和 Safari 缓存表现不同。
Chrome 中缓存正常
- 刷新。访问静态资源显示 (disk cache)
- 退出再访问。同上
Safari 中
- 刷新。访问静态资源会显示 (memory)
- 退出再访问。由于缓存在内存里,退出后就没了,导致静态资源重新请求
别的网站,比如百度的静态资源在 Safari 中缓存是 (disk),为什么我们网站的缓存却是在内存里,不能持久化呢?
经过一番排查最终定位,是因为我们项目中用了 Service Work 的 addEventListener('fetch') 导致的。
## 问题现场
出现问题时,我们的 service worker 脚本大致如下。下面的代码中,我们希望在匹配到某些的 url 时手动管理缓存,对其他请求不做任何处理。
```ts
// service-work.js
const CACHE_NAME = 'image-cache-v1'
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const pathname = decodeURIComponent(url.pathname)
if (
// some rule
) {
const params = new URLSearchParams()
const cacheKey = url.pathname + '?' + params.toString()
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(cacheKey).then((response) => {
return (
response ||
fetch(event.request).then((networkResponse) => {
cache.put(cacheKey, networkResponse.clone())
return networkResponse
})
)
})
})
)
})
```
根据我们的代码,if 条件外的请求应该与正常的 HTTP 请求缓存行为一致。在 Cache-Control 的控制下,缓存在 disk 上。
Chrome 中没有问题,但在 Safari 中却变成了 memory cache。
这种行为与 https://web.dev/articles/service-worker-caching-and-http-caching 中的介绍不一致,我还没有找到 Safari 这方面的具体文档或解释。
## 解决方式
最后我们为 addEventListener('fetch') 添加一个兜底,对所有请求都手动调用 fetch,并检查在 Service Work 中的缓存,解决了这个问题。
经测试,手动调用 fetch 后,Safari 的 Service Worker 缓存是可以持久化的。
```javascript
// service-work.js
const CACHE_NAME = 'image-cache-v1'
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const pathname = decodeURIComponent(url.pathname)
if (
// some rule
) {
const params = new URLSearchParams()
params.set('x-oss-process', url.searchParams.get('x-oss-process'))
const cacheKey = url.pathname + '?' + params.toString()
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(cacheKey).then((response) => {
return (
response ||
fetch(event.request).then((networkResponse) => {
cache.put(cacheKey, networkResponse.clone())
return networkResponse
})
)
})
})
)
} else {
// 手动检查缓存
event.respondWith(
caches.match(event.request).then(function (response) {
return response || fetch(event.request)
})
)
}
})
```
## 结论
当使用 Service Work 监听 fetch 时,在 Safari 上会导致默认请求的缓存写在内存上。如果想避免这种问题,可以在Service Work 中手动调用 fetch 并检查 Service 的缓存。
> 在这次排除 bug 中,还发现在开启了代理的情况下,Safari 也会将资源只缓存到内存中。